iT邦幫忙

2022 iThome 鐵人賽

DAY 7
1
Software Development

讓 C# 也可以很 Social - 在 .NET 6 用 C# 串接 LINE Services API 的取經之路系列 第 7

[Day 7] 讓 C# 也可以很 Social - .NET 6 C# 與 Line Services API 開發 - 回覆與廣播訊息

  • 分享至 

  • xImage
  •  
tags: .NET6 C#, LineBot, Line Messaging API, C#, dotnet core

[Day 7] 讓 C# 也可以很 Social - .NET 6 C# 與 Line Services API 開發 - 回覆與廣播訊息

前言

Hihi 各位好,今天開始進入訊息的介紹了~預計連續8至9篇的內容,詳細的為各位講解 Line Bot Messaging api 傳訊機制~

  • 今天的主題內容會是建立 廣播訊息功能使用 ReplyToken 回覆使用者文字訊息

訊息推播簡介

  • 傳訊息給使用者的方式簡單分為兩種

    1. Reply (透過 webhook event 傳入的 replyToken 回覆訊息)
    2. Push (主動推播)
      • 而主動推播又有 4 種不同的方法
      • Push message (推播訊息給一位使用者,one-to-one)
      • Multicast messages (根據一個 UserID 清單推播訊息給多位使用者,one-to-many)
      • Narrowcast messages (推播訊息給特定標籤或預設好的受眾,one-to-many)
      • Broadcast messages (推播訊息給 Line bot 的所有好友,one-to-many)
    • 其中主動傳訊的功能並不是能無限制數量傳送的,免費方案的 Line Bot 每個月只有 500 則訊息的用量,達到 500 則訊息後則不能再傳送,而另外的升級方案需要通過驗證才能購買,這邊就不說明了,有興趣再去了解吧。
    • 不過若是使用 Reply 的方式傳送訊息就是完全免費的! 這是因為 Line 希望 Line Bot 是一個能夠有與使用者有很多互動的存在,所以透過使用者與 Line Bot 互動產生 webhook event ,使用其中夾帶的 replyToken 就可以直接回覆這個事件,傳送訊息給使用者,也不消耗免費訊息則數!
  • 能夠傳送的訊息種類為以下 9 種

    1. Text message
    2. Sticker message
    3. Image message
    4. Video message
    5. Audio message
    6. Location message
    7. Imagemap message
    8. Template message
    9. Flex message
  • 訊息的詳細介紹下一篇再開始,這篇我們先建立傳送訊息的基本機制,這樣下一篇就可以邊實作邊測試了~


基本 Message Class 宣告

這邊我直接貼上要基礎架構的 class 宣告,後面會因應情況再逐漸地加入細節~

  • 先新增 Dtos/Messages 資料夾

BaseMessageDto

  • 在 Dtos/Messages 資料夾中新增 BaseMessageDto.cs
  • (這個 BaseMessageDto也是基礎架構的一部份,後面會因應情況再逐漸地加入更多細節)
namespace LineBotMessage.Dtos
{
    public class BaseMessageDto
    {
        public string Type { get; set; }
    }
}

MessageTypeEnum

  • 在 Enum 資料夾中新增 MessageTypeEnum.cs
  • 先準備好會未來會用到的 enum
namespace LineBotMessage.Enum
{
    public static class MessageTypeEnum
    {
        public const string Text = "text";
        public const string Sticker = "sticker";
        public const string Image = "image";
        public const string Video = "video";
        public const string Audio = "audio";
        public const string Location = "location";
        public const string Imagemap = "imagemap";
        public const string Template = "template";
        public const string Flex = "flex";
    }
}

TextMessageDto

  • 在 Dtos/Messages 資料夾中新增 TextMessageDto.cs
  • (先列出 基本架構的程式碼,後續的細節再逐步加入更多細節)
using LineBotMessage.Enum;
namespace LineBotMessage.Dtos
{
    public class TextMessageDto : BaseMessageDto
    {
        public TextMessageDto()
        {
            Type = MessageTypeEnum.Text;
        }
        public string Text { get; set; }
    }
}

建立傳訊機制

  • 這一篇我們就簡單的以能夠廣播訊息 & 回覆使用者為目標來建立傳訊機制

新增 JsonProvider

  • 因為 Line 接收格式的變數名稱開頭為小寫,但 C# 習慣的命名規則 Class 變數名稱為大寫開頭,所以在傳送 Json 給 Line 的過程中 無法直接使用 JsonSerializer 做資料的處理(序列化/反序列化),所以我們需要加工一下,自己包一個 Provider 來處理 Json 轉換的問題。(會在下面的實作 用到)

  • 新增 Providers 資料夾,並新增 JsonProvider.cs 檔案

using System.Text.Json;
using System.Text.Json.Serialization;

namespace LineBotMessage.Providers
{
    public class JsonProvider
    {
        private JsonSerializerOptions serializeOption = new JsonSerializerOptions()
        {
**            PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
            PropertyNameCaseInsensitive = true,
            DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
            Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
**        };

        private static JsonSerializerOptions deserializeOptions = new JsonSerializerOptions
        {
            PropertyNameCaseInsensitive = true,
        };

        public string Serialize<T>(T obj)
        {
            return JsonSerializer.Serialize(obj, serializeOption);
        }

        public T Deserialize<T>(string str)
        {
            return JsonSerializer.Deserialize<T>(str, deserializeOptions);
        }
    }
}

LineBotService 宣告變數

  • 將以下變數加進 LineBotService 中
// 宣告變數
private readonly string replyMessageUri = "https://api.line.me/v2/bot/message/reply";
private readonly string broadcastMessageUri = "https://api.line.me/v2/bot/message/broadcast";
private static HttpClient client = new HttpClient(); // 負責處理HttpRequest
private readonly JsonProvider _jsonProvider = new JsonProvider(); 

建立 Broadcast 機制 文件連結

廣播訊息功能為在 LineBotContorller 上新增一支 API 去負責接收廣播請求並將該請求送至 LineBotService 轉換成 Line 要求的 Http request 格式送至 Line,最後成功送出廣播訊息。

  • 此畫面可以得知,送出廣播訊息的 request 時需要的資訊為


  • 以下則對 Headers & Request body 的格式與內容作出說明

程式實作

  • 在 Dtos/Messages 資料夾下新增 Request 資料夾,並新增 BroadcastMessageRequestDto.cs 然後依照文件中的資訊建立 Class 屬性。
namespace LineBotMessage.Dtos
{
    public class BroadcastMessageRequestDto<T>
    {
        public List<T> Messages { get; set; }
        public bool? NotificationDisabled { get; set; }
    }
}
  • 各位會發現 Messages 的型別是 List<T> 使用到了泛型,這是因為訊息的屬性非常多種的原因,在傳送時先決定好目前要傳送的訊息類型能夠比較簡單直接的實作功能。

  • 在 LineBotService 中加入以下兩個 function

/// <summary>
/// 接收到廣播請求時,在將請求傳至 Line 前多一層處理,依據收到的 messageType 將 messages 轉換成正確的型別,這樣 Json 轉換時才能正確轉換。
/// </summary>
/// <param name="messageType"></param>
/// <param name="requestBody"></param>
public void BroadcastMessageHandler(string messageType, object requestBody)
{
    string strBody = requestBody.ToString();
    switch (messageType)
    {
        case MessageTypeEnum.Text:
            var messageRequest = _jsonProvider.Deserialize<BroadcastMessageRequestDto<TextMessageDto>>(strBody);
            BroadcastMessage(messageRequest);
            break;
    }

}

/// <summary>
/// 將廣播訊息請求送到 Line
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="request"></param>
public async void BroadcastMessage<T>(BroadcastMessageRequestDto<T> request)
{
    client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", channelAccessToken); //帶入 channel access token
    var json = _jsonProvider.Serialize(request);
    var requestMessage = new HttpRequestMessage
    {
        Method = HttpMethod.Post,
        RequestUri = new Uri(broadcastMessageUri),
        Content = new StringContent(json, Encoding.UTF8, "application/json")
    };

    var response = await client.SendAsync(requestMessage);
    Console.WriteLine(await response.Content.ReadAsStringAsync());
}
  • 最後在 LineBotController 新增接收API
[HttpPost("SendMessage/Broadcast")]
public IActionResult Broadcast([Required] string messageType, object body)
{
    _lineBotService.BroadcastMessageHandler(messageType, body);
    return Ok();
}

測試

  • 在 Swagger 上進行廣播測試

  • 廣播訊息傳送結果



建立 Reply 機制 文件連結

  • 此畫面可以得知,送出回覆訊息的 request 時需要的資訊。


  • 以下則對 Headers & Request body 的格式與內容作出說明

程式實作

  • 在 Dtos/Messages/Request 資料夾新增 ReplyMessageRequestDto.cs 依照文件中的資訊建立 Class 屬性。
namespace LineBotMessage.Dtos
{
    public class ReplyMessageRequestDto<T>
    {
        public string ReplyToken { get; set; }
        public List<T> Messages { get; set; }
        public bool? NotificationDisabled { get;set; }
    }
}
  • LineBotService 加入以下兩個 function
/// <summary>
/// 接收到回覆請求時,在將請求傳至 Line 前多一層處理(目前為預留)
/// </summary>
/// <param name="messageType"></param>
/// <param name="requestBody"></param>
public void ReplyMessageHandler<T>(string messageType, ReplyMessageRequestDto<T> requestBody)
{
    ReplyMessage(requestBody);
}

/// <summary>
/// 將回覆訊息請求送到 Line
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="request"></param>    
public async void ReplyMessage<T>(ReplyMessageRequestDto<T> request)
{
    client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", channelAccessToken); //帶入 channel access token
    var json = _jsonProvider.Serialize(request);
    var requestMessage = new HttpRequestMessage
    {
        Method = HttpMethod.Post,
        RequestUri = new Uri(replyMessageUri),
        Content = new StringContent(json, Encoding.UTF8, "application/json")
    };

    var response = await client.SendAsync(requestMessage);
    Console.WriteLine(await response.Content.ReadAsStringAsync());
}

測試

  • 要使用回覆功能,要在接收到 webhook event 時帶入收到的 replyToken 進行回覆。

  • 在 LineBotService - ReceiveWebhook function 中修改收到 Message Event 時的動作。

case WebhookEventTypeEnum.Message:
    var replyMessage = new ReplyMessageRequestDto<TextMessageDto>()
    {
        ReplyToken = eventObject.ReplyToken,
        Messages = new List<TextMessageDto>
        {
            new TextMessageDto(){Text = $"您好,您傳送了\"{eventObject.Message.Text}\"!"}
        }
    };
    ReplyMessageHandler("text",replyMessage);
    break;
  • 實際效果

結語

撒花 ~ 今天的程式內容依舊蠻多的,希望能讓各位簡單地看懂流程建立的方式,下一篇開始會詳細的介紹 Line 提供的訊息格式並帶大家實作,中間有可能會穿插一些相關知識或是簡單的API串接,請各位期待吧!

腦筋急轉彎

  • Messages 使用泛型的原因? 不使用泛型要怎麼正確做 Json 序列化。
  • HttpClient 使用 static 的原因?
  • 如果使用 HttpClientFactory 的話怎麼用?
  • Enum 列舉裏面的數值,除了數字以外,可以使用字串嗎?

範例程式碼

如果想要參考今天範例程式碼的部份,下面是 Git Repo 連結,方便大家參考。

Day7_Message Introducing & Broadcasting


上一篇
[Day 6] 讓 C# 也可以很 Social - .NET 6 C# 與 Line Services API 開發 - Webhook events 介紹(二)
下一篇
[Day 8] 讓 C# 也可以很 Social - .NET 6 C# 與 Line Services API 開發 - Text、Sticker、Image Message
系列文
讓 C# 也可以很 Social - 在 .NET 6 用 C# 串接 LINE Services API 的取經之路30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
gaindran
iT邦新手 5 級 ‧ 2023-02-01 23:35:27

您好,我照著範例打,第一次有成功執行到LINE上答覆收到留言,但是關機再次上線之後不知道哪邊出了問題,LINE端就只會顯示已讀而沒有收到server端的回應訊息了,可以教我可以用什麼方式怎麼查出問題點嗎?
1.我是用Visual Studio 2022+ngrok,LINE developers上的messagingAPI驗證是回應"成功OK"的,一直持續從ngrok/inspect都可以看到確實有success回傳200 OK給LINE,也有確實把ngrok的網址正確更新到LINE developers的設定中。
2.但是不管在LINE上或者用SwaggerUI測試,LINE都收不到訊息,從Visual Studio中設定中斷點追蹤到await client.SendAsync(requestMessage)這行時,response變數就會得到StatusCode:400,ReasonPhrase: 'Bad Request'的錯誤訊息,確實沒有拋回資料,SwaggerUI和Ngrok可以看到這個POST api/LineBOt/Webhook一直處於等候中的狀態。

APPX Jim iT邦新手 5 級 ‧ 2023-02-03 11:04:18 檢舉

您好~
以收到 400 Bad Request 時,會檢查傳過去的參數名稱、型別是否有問題~

gaindran iT邦新手 5 級 ‧ 2023-02-03 23:51:10 檢舉

一、哈! 謝謝,我找到了,果然是我在練習修改時,不小心在WebhookEventDto類別中誤加抄了一個Id變數進去。
二、回饋一個發現給您參考: 在今天的範例中的LineBotController類別裡,有額外宣告了一個沒有用到的_jsonProvider。

APPX Jim iT邦新手 5 級 ‧ 2023-02-06 10:53:14 檢舉

收到了,感謝回饋!!
會再把它移出~

我要留言

立即登入留言